/*-
 *   BSD LICENSE
 *
 *   Copyright(c) 2015 Intel Corporation. All rights reserved.
 *   All rights reserved.
 *
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *     * Neither the name of Intel Corporation nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdint.h>
#include <inttypes.h>
#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_cycles.h>
#include <rte_lcore.h>
#include <rte_mbuf.h>
#include <rte_ip.h>
#include <rte_udp.h>
#include <limits.h>
#include <sys/time.h>
#include <getopt.h>
#include <unistd.h>

#define RX_RING_SIZE 128
#define TX_RING_SIZE 128

#define NUM_MBUFS            8191
#define MBUF_CACHE_SIZE       250


struct rte_mempool *mbuf_pool;

static const struct rte_eth_conf port_conf_default = {
	.rxmode = { .max_rx_pkt_len = ETHER_MAX_LEN },
        .txmode = {
                .mq_mode = ETH_MQ_TX_NONE,
        },
};


/*
 * Initializes a given port using global settings and with the RX buffers
 * coming from the mbuf_pool passed as a parameter.
 */
static inline int
port_init(uint8_t port, struct rte_mempool *mbuf_pool)
{
	struct rte_eth_dev_info dev_info;
	struct rte_eth_conf port_conf = port_conf_default;
	const uint16_t rx_rings = 1;
	const uint16_t tx_rings = 1;
	int retval;
	uint16_t q;

	if (port >= rte_eth_dev_count())
		return -1;

	/* Configure the Ethernet device. */
	retval = rte_eth_dev_configure(port, rx_rings, tx_rings, &port_conf);
	if (retval != 0)
		return retval;

	/* Allocate and set up 1 RX queue per Ethernet port. */
	for (q = 0; q < rx_rings; q++) {
		retval = rte_eth_rx_queue_setup(port, q, RX_RING_SIZE,
				rte_eth_dev_socket_id(port), NULL, mbuf_pool);

		if (retval < 0)
			return retval;
	}

	/* Allocate and set up 1 TX queue per Ethernet port. */
	for (q = 0; q < tx_rings; q++) {
		/* Setup txq_flags */
		struct rte_eth_txconf *txconf;

		rte_eth_dev_info_get(q, &dev_info);
		txconf = &dev_info.default_txconf;
		txconf->txq_flags = 0;
		printf("tx_thresh.pthresh: %x\n", txconf->tx_thresh.pthresh);
		printf("tx_thresh.hthresh: %x\n", txconf->tx_thresh.hthresh);
		printf("tx_thresh.wthresh: %x\n", txconf->tx_thresh.wthresh);
		printf("tx_rs_thresh: %x\n", txconf->tx_rs_thresh);
		printf("tx_free_thresh: %x\n", txconf->tx_free_thresh);
		printf("txq_flags: %x\n", txconf->txq_flags);
		printf("tx_deferred_start: %x\n", txconf->tx_deferred_start);

		txconf->tx_thresh.pthresh = 1;
		txconf->tx_thresh.hthresh = 1;
		txconf->tx_thresh.wthresh = 1;
		txconf->tx_rs_thresh = 1;
		txconf->tx_free_thresh = 1;

		retval = rte_eth_tx_queue_setup(port, q, TX_RING_SIZE,
				rte_eth_dev_socket_id(port), txconf);
		if (retval < 0)
			return retval;
	}

	/* Start the Ethernet port. */
	retval = rte_eth_dev_start(port);
	if (retval < 0)
		return retval;

	/* Enable timesync timestamping for the Ethernet device */
	rte_eth_timesync_enable(port);

	/* Enable RX in promiscuous mode for the Ethernet device. */
	rte_eth_promiscuous_enable(port);

	return 0;
}

static void send_pkt(int portid, int id)
{
	struct rte_mbuf *created_pkt;
	struct ether_hdr *eth_hdr;
	struct ipv4_hdr *iph;
	struct udp_hdr* uh;
	char* data;
	int ret;

	created_pkt = rte_pktmbuf_alloc(mbuf_pool);
#define DATA_LEN 20

	int pkt_size = sizeof(struct ether_hdr) + sizeof(struct ipv4_hdr)  + sizeof(struct udp_hdr) + DATA_LEN;
	created_pkt->data_len = pkt_size;
	created_pkt->pkt_len = pkt_size;
	eth_hdr = rte_pktmbuf_mtod(created_pkt, struct ether_hdr *);
	rte_eth_macaddr_get(portid, &eth_hdr->s_addr);

	//ether_addr_copy("\x00\x17\x17\x17\x17\x17", &eth_hdr->d_addr);
	memcpy(&eth_hdr->d_addr, "\x00\x17\x17\x17\x17\x17", 6);

	eth_hdr->ether_type = htons(0x0800);

	iph = (struct ipv4_hdr *)(rte_pktmbuf_mtod(created_pkt, char *) + sizeof(struct ether_hdr));
	iph->version_ihl = 0x45;
	iph->type_of_service = 0;
	iph->total_length = htons(pkt_size - 14);
	iph->packet_id = 0x1122;
	iph->fragment_offset = 0;
	iph->time_to_live = 0x80;
	iph->next_proto_id = 0x11;
	iph->hdr_checksum = 0x92fa;
	iph->src_addr = 0x01020304;
	iph->dst_addr = 0x0a0b0c0d;

	uh = (struct udp_hdr *)((char*)iph + sizeof(struct ipv4_hdr));
	uh->src_port = htons(80);
	uh->dst_port = htons(9818);
	uh->dgram_len = htons(DATA_LEN + 8);
	uh->dgram_cksum = 0xc911;

	data = (char*)uh + sizeof(struct udp_hdr);
	snprintf(data, DATA_LEN, "message %d from dpdk", id), 

	/* Enable flag for hardware timestamping. */
	//created_pkt->ol_flags |= PKT_TX_IEEE1588_TMST;
	created_pkt->ol_flags = 0;
        //created_pkt->l2_len = sizeof(struct ether_hdr);
        //created_pkt->l3_len = sizeof(struct ipv4_hdr);

	/* Transmit the packet. */
	ret = rte_eth_tx_burst(portid, 0, &created_pkt, 1);
	if(ret)
		printf("send %d pkt %lu\n", ret, time(NULL));
	else
	{
		rte_pktmbuf_free(created_pkt);
		printf("send %d pkt %lu xxxxxxxxxxxxxxxx\n", ret, time(NULL));
	}
}

/*
 * The lcore main. This is the main thread that does the work, reading from an
 * input port and writing to an output port.
 */
static /*__attribute__((noreturn))*/ void
lcore_main(void)
{
	uint8_t portid;
	int i;

	const uint8_t nb_ports = rte_eth_dev_count();

	printf("waiting to start ...\n");
	sleep(3);

	/*
	 * Check that the port is on the same NUMA node as the polling thread
	 * for best performance.
	 */
	printf("\nCore %u Sending packets. [Ctrl+C to quit]\n",
			rte_lcore_id());

	for(i = 0; i < 10; i++) {
		for (portid = 0; portid < nb_ports; portid++) {
			printf("Sending %d packet to port %d\n", i, portid);
			send_pkt(portid, i);
		}
	}

	while(1)
		sleep(1);
}

/*
 * The main function, which does initialization and calls the per-lcore
 * functions.
 */
int
main(int argc, char *argv[])
{
	unsigned nb_ports;

	uint8_t portid;

	/* Initialize the Environment Abstraction Layer (EAL). */
	int ret = rte_eal_init(argc, argv);

	if (ret < 0)
		rte_exit(EXIT_FAILURE, "Error with EAL initialization\n");

	argc -= ret;
	argv += ret;

	/* Check that there is an even number of ports to send/receive on. */
	nb_ports = rte_eth_dev_count();

	/* Creates a new mempool in memory to hold the mbufs. */
	mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports,
		MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());

	if (mbuf_pool == NULL)
		rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n");

	/* Initialize all ports. */
	for (portid = 0; portid < nb_ports; portid++) {
		if (port_init(portid, mbuf_pool) != 0)  {
			rte_exit(EXIT_FAILURE,
				 "Cannot init port %"PRIu8 "\n",
				 portid);
		}
	}

	if (rte_lcore_count() > 1)
		printf("\nWARNING: Too many lcores enabled. Only 1 used.\n");

	/* Call lcore_main on the master core only. */
	lcore_main();

	return 0;
}
